/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for
 *       bug "inline method - doesn't handle implicit cast" (see
 *       https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941).
 *     Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for
 *       bug Encapsulate field can fail when two variables in one variable declaration (see
 *       https://bugs.eclipse.org/bugs/show_bug.cgi?id=51540).
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
import org.eclipse.jdt.internal.ui.preferences.MembersOrderPreferenceCache;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.TextEdit;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * JDT-UI-internal helper methods that deal with {@link org.eclipse.jdt.core.dom.ASTNode}s:
 * <ul>
 * <li>additional operations on {@link org.eclipse.jdt.core.dom.ASTNode}s and subtypes</li>
 * <li>finding related nodes in an AST</li>
 * <li>some methods that deal with bindings (new such methods should go into {@link Bindings})</li>
 * </ul>
 *
 */
public class ASTNodes {

    public static final int NODE_ONLY            = 0;
    public static final int INCLUDE_FIRST_PARENT = 1;
    public static final int INCLUDE_ALL_PARENTS  = 2;

    public static final int WARNING  = 1 << 0;
    public static final int ERROR    = 1 << 1;
    public static final int PROBLEMS = WARNING | ERROR;

    private static final Message[]  EMPTY_MESSAGES = new Message[0];
    private static final IProblem[] EMPTY_PROBLEMS = new IProblem[0];

    private static final int CLEAR_VISIBILITY = ~(Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE);


    private ASTNodes() {
        // no instance;
    }

    public static String asString(ASTNode node) {
        ASTFlattener flattener = new ASTFlattener();
        node.accept(flattener);
        return flattener.getResult();
    }

    public static String asFormattedString(ASTNode node, int indent, String lineDelim, Map<String, String> options) {
        String unformatted = asString(node);
        TextEdit edit = CodeFormatterUtil.format2(node, unformatted, indent, lineDelim, options);
        if (edit != null) {
            Document document = new Document(unformatted);
            try {
                edit.apply(document, TextEdit.NONE);
            } catch (BadLocationException e) {
                JavaPlugin.log(e);
            }
            return document.get();
        }
        return unformatted; // unknown node
    }


    /**
     * Returns the source of the given node from the location where it was parsed.
     * @param node the node to get the source from
     * @param extendedRange if set, the extended ranges of the nodes should ne used
     * @param removeIndent if set, the indentation is removed.
     * @return return the source for the given node or null if accessing the source failed.
     */
    public static String getNodeSource(ASTNode node, boolean extendedRange, boolean removeIndent) {
        ASTNode root = node.getRoot();
        if (root instanceof CompilationUnit) {
            CompilationUnit astRoot = (CompilationUnit)root;
            ITypeRoot typeRoot = astRoot.getTypeRoot();
            try {
                if (typeRoot != null && typeRoot.getBuffer() != null) {
                    IBuffer buffer = typeRoot.getBuffer();
                    int offset = extendedRange ? astRoot.getExtendedStartPosition(node) : node.getStartPosition();
                    int length = extendedRange ? astRoot.getExtendedLength(node) : node.getLength();
                    String str = buffer.getText(offset, length);
                    if (removeIndent) {
                        IJavaProject project = typeRoot.getJavaProject();
                        int indent = StubUtility.getIndentUsed(buffer, node.getStartPosition(), project);
                        str = Strings.changeIndent(str, indent, project, new String(), typeRoot.findRecommendedLineSeparator());
                    }
                    return str;
                }
            } catch (JavaModelException e) {
                // ignore
            }
        }
        return null;
    }

    /**
     * Returns the list that contains the given ASTNode. If the node
     * isn't part of any list, <code>null</code> is returned.
     *
     * @param node the node in question
     * @return the list that contains the node or <code>null</code>
     */
    public static List<? extends ASTNode> getContainingList(ASTNode node) {
        StructuralPropertyDescriptor locationInParent = node.getLocationInParent();
        if (locationInParent != null && locationInParent.isChildListProperty()) {
            return getChildListProperty(node.getParent(), (ChildListPropertyDescriptor)locationInParent);
        }
        return null;
    }

    /**
     * Variant of {@link org.eclipse.jdt.core.dom.ASTNode#getStructuralProperty(org.eclipse.jdt.core.dom.StructuralPropertyDescriptor)}
     * that avoids
     * unchecked casts in the caller.
     * <p>
     * To improve type-safety, callers can add the expected element type as explicit type argument, e.g.:
     * <p>
     * {@code ASTNodes.<BodyDeclaration>getChildListProperty(typeDecl, bodyDeclarationsProperty)}
     *
     * @param node the node
     * @param propertyDescriptor the child list property to get
     * @return the child list
     * @exception RuntimeException if this node does not have the given property
     */
    @SuppressWarnings("unchecked")
    public static <T extends ASTNode> List<T> getChildListProperty(ASTNode node, ChildListPropertyDescriptor propertyDescriptor) {
        return (List<T>)node.getStructuralProperty(propertyDescriptor);
    }

    /**
     * Returns a list of the direct children of a node. The siblings are ordered by start offset.
     * @param node the node to get the children for
     * @return the children
     */
    public static List<ASTNode> getChildren(ASTNode node) {
        ChildrenCollector visitor = new ChildrenCollector();
        node.accept(visitor);
        return visitor.result;
    }

    private static class ChildrenCollector extends GenericVisitor {
        public List<ASTNode> result;

        public ChildrenCollector() {
            super(true);
            result = null;
        }

        @Override
        protected boolean visitNode(ASTNode node) {
            if (result == null) { // first visitNode: on the node's parent: do nothing, return true
                result = new ArrayList<ASTNode>();
                return true;
            }
            result.add(node);
            return false;
        }
    }

    /**
     * Returns true if this is an existing node, i.e. it was created as part of
     * a parsing process of a source code file. Returns false if this is a newly
     * created node which has not yet been given a source position.
     *
     * @param node the node to be tested.
     * @return true if this is an existing node, false if not.
     */
    public static boolean isExistingNode(ASTNode node) {
        return node.getStartPosition() != -1;
    }

    /**
     * Returns the element type. This is a convenience method that returns its
     * argument if it is a simple type and the element type if the parameter is an array type.
     * @param type The type to get the element type from.
     * @return The element type of the type or the type itself.
     */
    public static Type getElementType(Type type) {
        if (!type.isArrayType())
            return type;
        return ((ArrayType)type).getElementType();
    }

    public static ASTNode findDeclaration(IBinding binding, ASTNode root) {
        root = root.getRoot();
        if (root instanceof CompilationUnit) {
            return ((CompilationUnit)root).findDeclaringNode(binding);
        }
        return null;
    }

    public static VariableDeclaration findVariableDeclaration(IVariableBinding binding, ASTNode root) {
        if (binding.isField())
            return null;
        ASTNode result = findDeclaration(binding, root);
        if (result instanceof VariableDeclaration)
            return (VariableDeclaration)result;

        return null;
    }

    /**
     * Returns the type node for the given declaration.
     *
     * @param declaration the declaration
     * @return the type node or <code>null</code> if the given declaration represents a type
     *         inferred parameter in lambda expression
     */
    public static Type getType(VariableDeclaration declaration) {
        if (declaration instanceof SingleVariableDeclaration) {
            return ((SingleVariableDeclaration)declaration).getType();
        } else if (declaration instanceof VariableDeclarationFragment) {
            ASTNode parent = ((VariableDeclarationFragment)declaration).getParent();
            if (parent instanceof VariableDeclarationExpression)
                return ((VariableDeclarationExpression)parent).getType();
            else if (parent instanceof VariableDeclarationStatement)
                return ((VariableDeclarationStatement)parent).getType();
            else if (parent instanceof FieldDeclaration)
                return ((FieldDeclaration)parent).getType();
            else if (parent instanceof LambdaExpression)
                return null;
        }
        Assert.isTrue(false, "Unknown VariableDeclaration"); //$NON-NLS-1$
        return null;
    }

    public static int getDimensions(VariableDeclaration declaration) {
        int dim = declaration.getExtraDimensions();
        if (declaration instanceof VariableDeclarationFragment && declaration.getParent() instanceof LambdaExpression) {
            LambdaExpression lambda = (LambdaExpression)declaration.getParent();
            IMethodBinding methodBinding = lambda.resolveMethodBinding();
            if (methodBinding != null) {
                ITypeBinding[] parameterTypes = methodBinding.getParameterTypes();
                int index = lambda.parameters().indexOf(declaration);
                ITypeBinding typeBinding = parameterTypes[index];
                return typeBinding.getDimensions();
            }
        } else {
            Type type = getType(declaration);
            if (type instanceof ArrayType) {
                dim += ((ArrayType)type).getDimensions();
            }
        }
        return dim;
    }

    public static List<IExtendedModifier> getModifiers(VariableDeclaration declaration) {
        Assert.isNotNull(declaration);
        if (declaration instanceof SingleVariableDeclaration) {
            return ((SingleVariableDeclaration)declaration).modifiers();
        } else if (declaration instanceof VariableDeclarationFragment) {
            ASTNode parent = declaration.getParent();
            if (parent instanceof VariableDeclarationExpression)
                return ((VariableDeclarationExpression)parent).modifiers();
            else if (parent instanceof VariableDeclarationStatement)
                return ((VariableDeclarationStatement)parent).modifiers();
        }
        return new ArrayList<IExtendedModifier>(0);
    }

    public static boolean isSingleDeclaration(VariableDeclaration declaration) {
        Assert.isNotNull(declaration);
        if (declaration instanceof SingleVariableDeclaration) {
            return true;
        } else if (declaration instanceof VariableDeclarationFragment) {
            ASTNode parent = declaration.getParent();
            if (parent instanceof VariableDeclarationExpression)
                return ((VariableDeclarationExpression)parent).fragments().size() == 1;
            else if (parent instanceof VariableDeclarationStatement)
                return ((VariableDeclarationStatement)parent).fragments().size() == 1;
        }
        return false;
    }

    public static boolean isLiteral(Expression expression) {
        int type = expression.getNodeType();
        return type == ASTNode.BOOLEAN_LITERAL || type == ASTNode.CHARACTER_LITERAL || type == ASTNode.NULL_LITERAL ||
               type == ASTNode.NUMBER_LITERAL || type == ASTNode.STRING_LITERAL || type == ASTNode.TYPE_LITERAL;
    }

    public static boolean isLabel(SimpleName name) {
        int parentType = name.getParent().getNodeType();
        return parentType == ASTNode.LABELED_STATEMENT ||
               parentType == ASTNode.BREAK_STATEMENT || parentType != ASTNode.CONTINUE_STATEMENT;
    }

    public static boolean isStatic(BodyDeclaration declaration) {
        return Modifier.isStatic(declaration.getModifiers());
    }

    public static List<BodyDeclaration> getBodyDeclarations(ASTNode node) {
        if (node instanceof AbstractTypeDeclaration) {
            return ((AbstractTypeDeclaration)node).bodyDeclarations();
        } else if (node instanceof AnonymousClassDeclaration) {
            return ((AnonymousClassDeclaration)node).bodyDeclarations();
        }
        // should not happen.
        Assert.isTrue(false);
        return null;
    }

    /**
     * Returns the structural property descriptor for the "bodyDeclarations" property
     * of this node (element type: {@link org.eclipse.jdt.core.dom.BodyDeclaration}).
     *
     * @param node the node, either an {@link org.eclipse.jdt.core.dom.AbstractTypeDeclaration} or an {@link org.eclipse.jdt.core.dom
     * .AnonymousClassDeclaration}
     * @return the property descriptor
     */
    public static ChildListPropertyDescriptor getBodyDeclarationsProperty(ASTNode node) {
        if (node instanceof AbstractTypeDeclaration) {
            return ((AbstractTypeDeclaration)node).getBodyDeclarationsProperty();
        } else if (node instanceof AnonymousClassDeclaration) {
            return AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY;
        }
        // should not happen.
        Assert.isTrue(false);
        return null;
    }

    /**
     * Returns the simple name of the type, followed by array dimensions.
     * Skips qualifiers, type arguments, and type annotations.
     * <p>
     * Does <b>not</b> work for WildcardTypes, etc.!
     *
     * @param type a type that has a simple name
     * @return the simple name, followed by array dimensions
     * @see #getSimpleNameIdentifier(org.eclipse.jdt.core.dom.Name)
     * @since 3.10
     */
    public static String getTypeName(Type type) {
        final StringBuffer buffer = new StringBuffer();
        ASTVisitor visitor = new ASTVisitor() {
            @Override
            public boolean visit(PrimitiveType node) {
                buffer.append(node.getPrimitiveTypeCode().toString());
                return false;
            }

            @Override
            public boolean visit(SimpleType node) {
                buffer.append(getSimpleNameIdentifier(node.getName()));
                return false;
            }

            @Override
            public boolean visit(QualifiedType node) {
                buffer.append(node.getName().getIdentifier());
                return false;
            }

            @Override
            public boolean visit(NameQualifiedType node) {
                buffer.append(node.getName().getIdentifier());
                return false;
            }

            @Override
            public boolean visit(ParameterizedType node) {
                node.getType().accept(this);
                return false;
            }

            @Override
            public void endVisit(ArrayType node) {
                for (int i = 0; i < node.dimensions().size(); i++) {
                    buffer.append("[]"); //$NON-NLS-1$
                }
            }
        };
        type.accept(visitor);
        return buffer.toString();
    }

    /**
     * Returns the (potentially qualified) name of a type, followed by array dimensions.
     * Skips type arguments and type annotations.
     *
     * @param type a type that has a name
     * @return the name, followed by array dimensions
     * @since 3.10
     */
    public static String getQualifiedTypeName(Type type) {
        final StringBuffer buffer = new StringBuffer();
        ASTVisitor visitor = new ASTVisitor() {
            @Override
            public boolean visit(SimpleType node) {
                buffer.append(node.getName().getFullyQualifiedName());
                return false;
            }

            @Override
            public boolean visit(QualifiedType node) {
                node.getQualifier().accept(this);
                buffer.append('.');
                buffer.append(node.getName().getIdentifier());
                return false;
            }

            @Override
            public boolean visit(NameQualifiedType node) {
                buffer.append(node.getQualifier().getFullyQualifiedName());
                buffer.append('.');
                buffer.append(node.getName().getIdentifier());
                return false;
            }

            @Override
            public boolean visit(ParameterizedType node) {
                node.getType().accept(this);
                return false;
            }

            @Override
            public void endVisit(ArrayType node) {
                for (int i = 0; i < node.dimensions().size(); i++) {
                    buffer.append("[]"); //$NON-NLS-1$
                }
            }
        };
        type.accept(visitor);
        return buffer.toString();
    }
//
    public static InfixExpression.Operator convertToInfixOperator(Assignment.Operator operator) {
        if (operator.equals(Assignment.Operator.PLUS_ASSIGN))
            return InfixExpression.Operator.PLUS;

        if (operator.equals(Assignment.Operator.MINUS_ASSIGN))
            return InfixExpression.Operator.MINUS;

        if (operator.equals(Assignment.Operator.TIMES_ASSIGN))
            return InfixExpression.Operator.TIMES;

        if (operator.equals(Assignment.Operator.DIVIDE_ASSIGN))
            return InfixExpression.Operator.DIVIDE;

        if (operator.equals(Assignment.Operator.BIT_AND_ASSIGN))
            return InfixExpression.Operator.AND;

        if (operator.equals(Assignment.Operator.BIT_OR_ASSIGN))
            return InfixExpression.Operator.OR;

        if (operator.equals(Assignment.Operator.BIT_XOR_ASSIGN))
            return InfixExpression.Operator.XOR;

        if (operator.equals(Assignment.Operator.REMAINDER_ASSIGN))
			return InfixExpression.Operator.REMAINDER;

		if (operator.equals(Assignment.Operator.LEFT_SHIFT_ASSIGN))
			return InfixExpression.Operator.LEFT_SHIFT;

		if (operator.equals(Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN))
			return InfixExpression.Operator.RIGHT_SHIFT_SIGNED;

		if (operator.equals(Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN))
			return InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED;

		Assert.isTrue(false, "Cannot convert assignment operator"); //$NON-NLS-1$
		return null;
	}

	/**
	 * Returns true if a node at a given location is a body of a control statement. Such body nodes are
	 * interesting as when replacing them, it has to be evaluates if a Block is needed instead.
	 * E.g. <code> if (x) do(); -> if (x) { do1(); do2() } </code>
	 *
	 * @param locationInParent Location of the body node
	 * @return Returns true if the location is a body node location of a control statement.
	 */
	public static boolean isControlStatementBody(StructuralPropertyDescriptor locationInParent) {
		return locationInParent == IfStatement.THEN_STATEMENT_PROPERTY
			|| locationInParent == IfStatement.ELSE_STATEMENT_PROPERTY
			|| locationInParent == ForStatement.BODY_PROPERTY
			|| locationInParent == EnhancedForStatement.BODY_PROPERTY
			|| locationInParent == WhileStatement.BODY_PROPERTY
			|| locationInParent == DoStatement.BODY_PROPERTY;
	}

	/**
	 * Returns the type to which an inlined variable initializer should be cast, or
	 * <code>null</code> if no cast is necessary.
	 *
	 * @param initializer the initializer expression of the variable to inline
	 * @param reference the reference to the variable (which is to be inlined)
	 * @return a type binding to which the initializer should be cast, or <code>null</code> iff no cast is necessary
	 * @since 3.6
	 */
	public static ITypeBinding getExplicitCast(Expression initializer, Expression reference) {
		ITypeBinding initializerType= initializer.resolveTypeBinding();
		ITypeBinding referenceType= reference.resolveTypeBinding();
		if (initializerType == null || referenceType == null)
			return null;

		if (initializerType.isPrimitive() && referenceType.isPrimitive() && ! referenceType.isEqualTo(initializerType)) {
			return referenceType;

		} else if (initializerType.isPrimitive() && ! referenceType.isPrimitive()) { // initializer is autoboxed
			ITypeBinding unboxedReferenceType= Bindings.getUnboxedTypeBinding(referenceType, reference.getAST());
			if (!unboxedReferenceType.isEqualTo(initializerType))
				return unboxedReferenceType;
			else if (needsExplicitBoxing(reference))
				return referenceType;

		} else if (! initializerType.isPrimitive() && referenceType.isPrimitive()) { // initializer is autounboxed
			ITypeBinding unboxedInitializerType= Bindings.getUnboxedTypeBinding(initializerType, reference.getAST());
			if (!unboxedInitializerType.isEqualTo(referenceType))
				return referenceType;

		} else if (initializerType.isRawType() && referenceType.isParameterizedType()) {
			return referenceType; // don't lose the unchecked conversion

		} else if (initializer instanceof LambdaExpression || initializer instanceof MethodReference) {
			if (isTargetAmbiguous(reference, isExplicitlyTypedLambda(initializer))) {
				return referenceType;
			} else {
				ITypeBinding targetType= getTargetType(reference);
				if (targetType == null || targetType != referenceType) {
					return referenceType;
				}
			}

		} else if (! TypeRules.canAssign(initializerType, referenceType)) {
			if (!Bindings.containsTypeVariables(referenceType))
				return referenceType;
		}

		return null;
	}

	/**
	 * Checks whether overloaded methods can result in an ambiguous method call or a semantic change when the
	 * <code>expression</code> argument is replaced with a poly expression form of the functional
	 * interface instance.
	 *
	 * @param expression the method argument, which is a functional interface instance
	 * @param expressionIsExplicitlyTyped <code>true</code> iff the intended replacement for <code>expression</code>
	 *         is an explicitly typed lambda expression (JLS8 15.27.1)
	 * @return <code>true</code> if overloaded methods can result in an ambiguous method call or a semantic change,
	 *         <code>false</code> otherwise
	 *
	 * @since 3.10
	 */
	public static boolean isTargetAmbiguous(Expression expression, boolean expressionIsExplicitlyTyped) {
		StructuralPropertyDescriptor locationInParent= expression.getLocationInParent();

		while (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY
				|| locationInParent == ConditionalExpression.THEN_EXPRESSION_PROPERTY
				|| locationInParent == ConditionalExpression.ELSE_EXPRESSION_PROPERTY) {
			expression= (Expression) expression.getParent();
			locationInParent= expression.getLocationInParent();
		}

		ASTNode parent= expression.getParent();
		IMethodBinding methodBinding;
		int argumentIndex;
		int argumentCount;
		Expression invocationQualifier= null;
		if (locationInParent == MethodInvocation.ARGUMENTS_PROPERTY) {
			MethodInvocation methodInvocation= (MethodInvocation) parent;
			methodBinding= methodInvocation.resolveMethodBinding();
			argumentIndex= methodInvocation.arguments().indexOf(expression);
			argumentCount= methodInvocation.arguments().size();
			invocationQualifier= methodInvocation.getExpression();
		} else if (locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY) {
			SuperMethodInvocation superMethodInvocation= (SuperMethodInvocation) parent;
			methodBinding= superMethodInvocation.resolveMethodBinding();
			argumentIndex= superMethodInvocation.arguments().indexOf(expression);
			argumentCount= superMethodInvocation.arguments().size();
			invocationQualifier= superMethodInvocation.getQualifier();
		} else if (locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY) {
			ConstructorInvocation constructorInvocation= (ConstructorInvocation) parent;
			methodBinding= constructorInvocation.resolveConstructorBinding();
			argumentIndex= constructorInvocation.arguments().indexOf(expression);
			argumentCount= constructorInvocation.arguments().size();
		} else if (locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY) {
			SuperConstructorInvocation superConstructorInvocation= (SuperConstructorInvocation) parent;
			methodBinding= superConstructorInvocation.resolveConstructorBinding();
			argumentIndex= superConstructorInvocation.arguments().indexOf(expression);
			argumentCount= superConstructorInvocation.arguments().size();
		} else if (locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY) {
			ClassInstanceCreation creation= (ClassInstanceCreation) parent;
			methodBinding= creation.resolveConstructorBinding();
			argumentIndex= creation.arguments().indexOf(expression);
			argumentCount= creation.arguments().size();
		} else if (locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY) {
			EnumConstantDeclaration enumConstantDecl= (EnumConstantDeclaration) parent;
			methodBinding= enumConstantDecl.resolveConstructorBinding();
			argumentIndex= enumConstantDecl.arguments().indexOf(expression);
			argumentCount= enumConstantDecl.arguments().size();
		} else {
			return false;
		}

		if (methodBinding != null) {
			ITypeBinding invocationTargetType;
			if (parent instanceof MethodInvocation || parent instanceof SuperMethodInvocation) {
				if (invocationQualifier != null) {
					invocationTargetType= invocationQualifier.resolveTypeBinding();
					if (invocationTargetType != null && parent instanceof SuperMethodInvocation) {
						invocationTargetType= invocationTargetType.getSuperclass();
					}
				} else {
					ITypeBinding enclosingType= getEnclosingType(parent);
					if (enclosingType != null && parent instanceof SuperMethodInvocation) {
						enclosingType= enclosingType.getSuperclass();
					}
					if (enclosingType != null) {
						IMethodBinding methodInHierarchy= Bindings.findMethodInHierarchy(enclosingType, methodBinding.getName(),
                                                                                         methodBinding.getParameterTypes());
						if (methodInHierarchy != null) {
							invocationTargetType= enclosingType;
						} else {
							invocationTargetType= methodBinding.getDeclaringClass();
						}
					} else {
						// not expected
						invocationTargetType= methodBinding.getDeclaringClass();
					}
				}
			} else {
				invocationTargetType= methodBinding.getDeclaringClass();
			}
			if (invocationTargetType != null) {
				TypeBindingVisitor visitor= new AmbiguousTargetMethodAnalyzer(invocationTargetType, methodBinding, argumentIndex,
                                                                              argumentCount, expressionIsExplicitlyTyped);
				return !(visitor.visit(invocationTargetType) && Bindings.visitHierarchy(invocationTargetType, visitor));
			}
		}

		return true;
	}

	private static class AmbiguousTargetMethodAnalyzer implements TypeBindingVisitor {
        private ITypeBinding   fDeclaringType;
        private IMethodBinding fOriginalMethod;
        private int            fArgIndex;
        private int            fArgumentCount;
        private boolean        fExpressionIsExplicitlyTyped;

        /**
         * @param declaringType the type binding declaring the <code>originalMethod</code>
         * @param originalMethod the method declaration binding corresponding to the method call
         * @param argumentIndex the index of the functional interface instance argument in the
         *            method call
         * @param argumentCount the number of arguments in the method call
         * @param expressionIsExplicitlyTyped <code>true</code> iff the intended replacement for <code>expression</code>
         *         is an explicitly typed lambda expression (JLS8 15.27.1)
         */
        public AmbiguousTargetMethodAnalyzer(ITypeBinding declaringType, IMethodBinding originalMethod, int argumentIndex,
                                             int argumentCount, boolean expressionIsExplicitlyTyped) {
            fDeclaringType = declaringType;
            fOriginalMethod = originalMethod;
            fArgIndex = argumentIndex;
            fArgumentCount = argumentCount;
            fExpressionIsExplicitlyTyped = expressionIsExplicitlyTyped;
        }

        public boolean visit(ITypeBinding type) {
            IMethodBinding[] methods = type.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                IMethodBinding candidate = methods[i];
                if (candidate.getMethodDeclaration() == fOriginalMethod.getMethodDeclaration()) {
                    continue;
                }
                ITypeBinding candidateDeclaringType = candidate.getDeclaringClass();
                if (fDeclaringType != candidateDeclaringType) {
                    int modifiers = candidate.getModifiers();
                    if (candidateDeclaringType.isInterface() && Modifier.isStatic(modifiers)) {
                        continue;
                    }
                    if (Modifier.isPrivate(modifiers)) {
                        continue;
                    }
                }
                if (fOriginalMethod.getName().equals(candidate.getName()) && !fOriginalMethod.overrides(candidate)) {
                    ITypeBinding[] originalParameterTypes = fOriginalMethod.getParameterTypes();
                    ITypeBinding[] candidateParameterTypes = candidate.getParameterTypes();

                    boolean couldBeAmbiguous;
                    if (originalParameterTypes.length == candidateParameterTypes.length) {
                        couldBeAmbiguous = true;
                    } else if (fOriginalMethod.isVarargs() || candidate.isVarargs()) {
                        int candidateMinArgumentCount = candidateParameterTypes.length;
                        if (candidate.isVarargs())
                            candidateMinArgumentCount--;
                        couldBeAmbiguous = fArgumentCount >= candidateMinArgumentCount;
                    } else {
                        couldBeAmbiguous = false;
                    }
                    if (couldBeAmbiguous) {
                        ITypeBinding parameterType = ASTResolving.getParameterTypeBinding(candidate, fArgIndex);
                        if (parameterType != null && parameterType.getFunctionalInterfaceMethod() != null) {
                            if (!fExpressionIsExplicitlyTyped) {
								/* According to JLS8 15.12.2.2, implicitly typed lambda expressions are not "pertinent to applicability"
								 * and hence potentially applicable methods are always "applicable by strict invocation",
								 * regardless of whether argument expressions are compatible with the method's parameter types or not.
								 * If there are multiple such methods, 15.12.2.5 results in an ambiguous method invocation.
								 */
                                return false;
                            }
							/* Explicitly typed lambda expressions are pertinent to applicability, and hence
							 * compatibility with the corresponding method parameter type is checked. And since this check
							 * separates functional interface methods by their void-compatibility state, functional interfaces
							 * with a different void compatibility are not applicable any more and hence can't cause
							 * an ambiguous method invocation.
							 */
                            ITypeBinding origParamType = ASTResolving.getParameterTypeBinding(fOriginalMethod, fArgIndex);
                            boolean originalIsVoidCompatible =
                                    Bindings.isVoidType(origParamType.getFunctionalInterfaceMethod().getReturnType());
                            boolean candidateIsVoidCompatible =
                                    Bindings.isVoidType(parameterType.getFunctionalInterfaceMethod().getReturnType());
                            if (originalIsVoidCompatible == candidateIsVoidCompatible) {
                                return false;
                            }
                        }
                    }
                }
            }
            return true;
        }
    }

    /**
     * Derives the target type defined at the location of the given expression if the target context
     * supports poly expressions.
     *
     * @param expression the expression at whose location the target type is required
     * @return the type binding of the target type defined at the location of the given expression
     *         if the target context supports poly expressions, or <code>null</code> if the target
     *         type could not be derived
     *
     * @since 3.10
     */
    public static ITypeBinding getTargetType(Expression expression) {
        ASTNode parent = expression.getParent();
        StructuralPropertyDescriptor locationInParent = expression.getLocationInParent();

        if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY ||
            locationInParent == SingleVariableDeclaration.INITIALIZER_PROPERTY) {
            return ((VariableDeclaration)parent).getName().resolveTypeBinding();

        } else if (locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY) {
            return ((Assignment)parent).getLeftHandSide().resolveTypeBinding();

        } else if (locationInParent == ReturnStatement.EXPRESSION_PROPERTY) {
            return getTargetTypeForReturnStmt((ReturnStatement)parent);

        } else if (locationInParent == ArrayInitializer.EXPRESSIONS_PROPERTY) {
            return getTargetTypeForArrayInitializer((ArrayInitializer)parent);

        } else if (locationInParent == MethodInvocation.ARGUMENTS_PROPERTY) {
            MethodInvocation methodInvocation = (MethodInvocation)parent;
            IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
            if (methodBinding != null) {
                return getParameterTypeBinding(expression, methodInvocation.arguments(), methodBinding);
            }

        } else if (locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY) {
            SuperMethodInvocation superMethodInvocation = (SuperMethodInvocation)parent;
            IMethodBinding superMethodBinding = superMethodInvocation.resolveMethodBinding();
            if (superMethodBinding != null) {
                return getParameterTypeBinding(expression, superMethodInvocation.arguments(), superMethodBinding);
            }

        } else if (locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY) {
            ConstructorInvocation constructorInvocation = (ConstructorInvocation)parent;
            IMethodBinding constructorBinding = constructorInvocation.resolveConstructorBinding();
            if (constructorBinding != null) {
                return getParameterTypeBinding(expression, constructorInvocation.arguments(), constructorBinding);
            }

        } else if (locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY) {
            SuperConstructorInvocation superConstructorInvocation = (SuperConstructorInvocation)parent;
            IMethodBinding superConstructorBinding = superConstructorInvocation.resolveConstructorBinding();
            if (superConstructorBinding != null) {
                return getParameterTypeBinding(expression, superConstructorInvocation.arguments(), superConstructorBinding);
            }

        } else if (locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY) {
            ClassInstanceCreation creation = (ClassInstanceCreation)parent;
            IMethodBinding creationBinding = creation.resolveConstructorBinding();
            if (creationBinding != null) {
                return getParameterTypeBinding(expression, creation.arguments(), creationBinding);
            }

        } else if (locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY) {
            EnumConstantDeclaration enumConstantDecl = (EnumConstantDeclaration)parent;
            IMethodBinding enumConstructorBinding = enumConstantDecl.resolveConstructorBinding();
            if (enumConstructorBinding != null) {
                return getParameterTypeBinding(expression, enumConstantDecl.arguments(), enumConstructorBinding);
            }

        } else if (locationInParent == LambdaExpression.BODY_PROPERTY) {
            IMethodBinding methodBinding = ((LambdaExpression)parent).resolveMethodBinding();
            if (methodBinding != null) {
                return methodBinding.getReturnType();
            }

        } else if (locationInParent == ConditionalExpression.THEN_EXPRESSION_PROPERTY ||
                   locationInParent == ConditionalExpression.ELSE_EXPRESSION_PROPERTY) {
            return getTargetType((ConditionalExpression)parent);

        } else if (locationInParent == CastExpression.EXPRESSION_PROPERTY) {
            return ((CastExpression)parent).getType().resolveBinding();

        } else if (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY) {
            return getTargetType((ParenthesizedExpression)parent);

        }
        return null;
    }

    private static ITypeBinding getParameterTypeBinding(Expression expression, List<Expression> arguments, IMethodBinding methodBinding) {
        int index = arguments.indexOf(expression);
        return ASTResolving.getParameterTypeBinding(methodBinding, index);
    }

    private static ITypeBinding getTargetTypeForArrayInitializer(ArrayInitializer arrayInitializer) {
        ASTNode initializerParent = arrayInitializer.getParent();
        while (initializerParent instanceof ArrayInitializer) {
            initializerParent = initializerParent.getParent();
        }
        if (initializerParent instanceof ArrayCreation) {
            return ((ArrayCreation)initializerParent).getType().getElementType().resolveBinding();
        } else if (initializerParent instanceof VariableDeclaration) {
            ITypeBinding typeBinding = ((VariableDeclaration)initializerParent).getName().resolveTypeBinding();
            if (typeBinding != null) {
                return typeBinding.getElementType();
            }
        }
        return null;
    }

    private static ITypeBinding getTargetTypeForReturnStmt(ReturnStatement returnStmt) {
        LambdaExpression enclosingLambdaExpr = ASTResolving.findEnclosingLambdaExpression(returnStmt);
        if (enclosingLambdaExpr != null) {
            IMethodBinding methodBinding = enclosingLambdaExpr.resolveMethodBinding();
            return methodBinding == null ? null : methodBinding.getReturnType();
        }
        MethodDeclaration enclosingMethodDecl = ASTResolving.findParentMethodDeclaration(returnStmt);
        if (enclosingMethodDecl != null) {
            IMethodBinding methodBinding = enclosingMethodDecl.resolveBinding();
            return methodBinding == null ? null : methodBinding.getReturnType();
        }
        return null;
    }

    /**
     * Returns whether an expression at the given location needs explicit boxing.
     *
     * @param expression the expression
     * @return <code>true</code> iff an expression at the given location needs explicit boxing
     * @since 3.6
     */
    private static boolean needsExplicitBoxing(Expression expression) {
        StructuralPropertyDescriptor locationInParent = expression.getLocationInParent();
        if (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY)
            return needsExplicitBoxing((ParenthesizedExpression)expression.getParent());

        if (locationInParent == ClassInstanceCreation.EXPRESSION_PROPERTY
            || locationInParent == FieldAccess.EXPRESSION_PROPERTY
            || locationInParent == MethodInvocation.EXPRESSION_PROPERTY)
            return true;

        return false;
    }

    private static boolean isExplicitlyTypedLambda(Expression expression) {
        if (!(expression instanceof LambdaExpression))
            return false;
        LambdaExpression lambda = (LambdaExpression)expression;
        List<VariableDeclaration> parameters = lambda.parameters();
        if (parameters.isEmpty())
            return true;
        return parameters.get(0) instanceof SingleVariableDeclaration;
    }

    /**
     * Returns the closest ancestor of <code>node</code> that is an instance of <code>parentClass</code>, or <code>null</code> if none.
     * <p>
     * <b>Warning:</b> This method does not stop at any boundaries like parentheses, statements, body declarations, etc.
     * The resulting node may be in a totally different scope than the given node.
     * Consider using one of the {@link ASTResolving}<code>.find(..)</code> methods instead.
     * </p>
     * @param node the node
     * @param parentClass the class of the sought ancestor node
     * @return the closest ancestor of <code>node</code> that is an instance of <code>parentClass</code>, or <code>null</code> if none
     */
    public static ASTNode getParent(ASTNode node, Class<? extends ASTNode> parentClass) {
        do {
            node = node.getParent();
        } while (node != null && !parentClass.isInstance(node));
        return node;
    }

    /**
     * Returns the closest ancestor of <code>node</code> whose type is <code>nodeType</code>, or <code>null</code> if none.
     * <p>
     * <b>Warning:</b> This method does not stop at any boundaries like parentheses, statements, body declarations, etc.
     * The resulting node may be in a totally different scope than the given node.
     * Consider using one of the {@link ASTResolving}<code>.find(..)</code> methods instead.
     * </p>
     * @param node the node
     * @param nodeType the node type constant from {@link org.eclipse.jdt.core.dom.ASTNode}
     * @return the closest ancestor of <code>node</code> whose type is <code>nodeType</code>, or <code>null</code> if none
     */
    public static ASTNode getParent(ASTNode node, int nodeType) {
        do {
            node = node.getParent();
        } while (node != null && node.getNodeType() != nodeType);
        return node;
    }
//
//    public static ASTNode findParent(ASTNode node, StructuralPropertyDescriptor[][] pathes) {
//        for (int p = 0; p < pathes.length; p++) {
//            StructuralPropertyDescriptor[] path = pathes[p];
//            ASTNode current = node;
//            int d = path.length - 1;
//            for (; d >= 0 && current != null; d--) {
//                StructuralPropertyDescriptor descriptor = path[d];
//                if (!descriptor.equals(current.getLocationInParent()))
//                    break;
//                current = current.getParent();
//            }
//            if (d < 0)
//                return current;
//        }
//        return null;
//    }

    /**
     * For {@link org.eclipse.jdt.core.dom.Name} or {@link org.eclipse.jdt.core.dom.Type} nodes, returns the topmost {@link org.eclipse
     * .jdt.core.dom.Type} node
     * that shares the same type binding as the given node.
     *
     * @param node
     *         an ASTNode
     * @return the normalized {@link org.eclipse.jdt.core.dom.Type} node or the original node
     */
    public static ASTNode getNormalizedNode(ASTNode node) {
        ASTNode current = node;
        // normalize name
        if (QualifiedName.NAME_PROPERTY.equals(current.getLocationInParent())) {
            current = current.getParent();
        }
        // normalize type
        if (QualifiedType.NAME_PROPERTY.equals(current.getLocationInParent())
            || SimpleType.NAME_PROPERTY.equals(current.getLocationInParent())
            || NameQualifiedType.NAME_PROPERTY.equals(current.getLocationInParent())) {
            current = current.getParent();
        }
        // normalize parameterized types
        if (ParameterizedType.TYPE_PROPERTY.equals(current.getLocationInParent())) {
            current = current.getParent();
        }
        return current;
    }

    /**
     * Returns <code>true</code> iff <code>parent</code> is a true ancestor of <code>node</code>
     * (i.e. returns <code>false</code> if <code>parent == node</code>).
     *
     * @param node node to test
     * @param parent assumed parent
     * @return <code>true</code> iff <code>parent</code> is a true ancestor of <code>node</code>
     */
    public static boolean isParent(ASTNode node, ASTNode parent) {
        Assert.isNotNull(parent);
        do {
            node = node.getParent();
            if (node == parent)
                return true;
        } while (node != null);
        return false;
    }

    public static int getExclusiveEnd(ASTNode node) {
        return node.getStartPosition() + node.getLength();
    }

    public static int getInclusiveEnd(ASTNode node) {
        return node.getStartPosition() + node.getLength() - 1;
    }

    public static IMethodBinding getMethodBinding(Name node) {
        IBinding binding = node.resolveBinding();
        if (binding instanceof IMethodBinding)
            return (IMethodBinding)binding;
        return null;
    }

    public static IVariableBinding getVariableBinding(Name node) {
        IBinding binding = node.resolveBinding();
        if (binding instanceof IVariableBinding)
            return (IVariableBinding)binding;
        return null;
    }

    public static IVariableBinding getLocalVariableBinding(Name node) {
        IVariableBinding result = getVariableBinding(node);
        if (result == null || result.isField())
            return null;

        return result;
    }

    public static IVariableBinding getFieldBinding(Name node) {
        IVariableBinding result = getVariableBinding(node);
        if (result == null || !result.isField())
            return null;

        return result;
    }

    public static ITypeBinding getTypeBinding(Name node) {
        IBinding binding = node.resolveBinding();
        if (binding instanceof ITypeBinding)
            return (ITypeBinding)binding;
        return null;
    }

    /**
     * Returns the receiver's type binding of the given method invocation.
     *
     * @param invocation method invocation to resolve type of
     * @return the type binding of the receiver
     */
    public static ITypeBinding getReceiverTypeBinding(MethodInvocation invocation) {
        ITypeBinding result = null;
        Expression exp = invocation.getExpression();
        if (exp != null) {
            return exp.resolveTypeBinding();
        } else {
            AbstractTypeDeclaration type = (AbstractTypeDeclaration)getParent(invocation, AbstractTypeDeclaration.class);
            if (type != null)
                return type.resolveBinding();
        }
        return result;
    }

    public static ITypeBinding getEnclosingType(ASTNode node) {
        while (node != null) {
            if (node instanceof AbstractTypeDeclaration) {
                return ((AbstractTypeDeclaration)node).resolveBinding();
            } else if (node instanceof AnonymousClassDeclaration) {
                return ((AnonymousClassDeclaration)node).resolveBinding();
            }
            node = node.getParent();
        }
        return null;
    }

    public static IProblem[] getProblems(ASTNode node, int scope, int severity) {
        ASTNode root = node.getRoot();
        if (!(root instanceof CompilationUnit))
            return EMPTY_PROBLEMS;
        IProblem[] problems = ((CompilationUnit)root).getProblems();
        if (root == node)
            return problems;
        final int iterations = computeIterations(scope);
        List<IProblem> result = new ArrayList<IProblem>(5);
        for (int i = 0; i < problems.length; i++) {
            IProblem problem = problems[i];
            boolean consider = false;
            if ((severity & PROBLEMS) == PROBLEMS)
                consider = true;
            else if ((severity & WARNING) != 0)
                consider = problem.isWarning();
            else if ((severity & ERROR) != 0)
                consider = problem.isError();
            if (consider) {
                ASTNode temp = node;
                int count = iterations;
                do {
                    int nodeOffset = temp.getStartPosition();
                    int problemOffset = problem.getSourceStart();
                    if (nodeOffset <= problemOffset && problemOffset < nodeOffset + temp.getLength()) {
                        result.add(problem);
                        count = 0;
                    } else {
                        count--;
                    }
                } while ((temp = temp.getParent()) != null && count > 0);
            }
        }
        return result.toArray(new IProblem[result.size()]);
    }

    public static Message[] getMessages(ASTNode node, int flags) {
        ASTNode root = node.getRoot();
        if (!(root instanceof CompilationUnit))
            return EMPTY_MESSAGES;
        Message[] messages = ((CompilationUnit)root).getMessages();
        if (root == node)
            return messages;
        final int iterations = computeIterations(flags);
        List<Message> result = new ArrayList<Message>(5);
        for (int i = 0; i < messages.length; i++) {
            Message message = messages[i];
            ASTNode temp = node;
            int count = iterations;
            do {
                int nodeOffset = temp.getStartPosition();
                int messageOffset = message.getStartPosition();
                if (nodeOffset <= messageOffset && messageOffset < nodeOffset + temp.getLength()) {
                    result.add(message);
                    count = 0;
                } else {
                    count--;
                }
            } while ((temp = temp.getParent()) != null && count > 0);
        }
        return result.toArray(new Message[result.size()]);
    }

    private static int computeIterations(int flags) {
        switch (flags) {
            case NODE_ONLY:
                return 1;
            case INCLUDE_ALL_PARENTS:
                return Integer.MAX_VALUE;
            case INCLUDE_FIRST_PARENT:
                return 2;
            default:
                return 1;
        }
    }


    private static int getOrderPreference(BodyDeclaration member, MembersOrderPreferenceCache store) {
        int memberType = member.getNodeType();
        int modifiers = member.getModifiers();

        switch (memberType) {
            case ASTNode.TYPE_DECLARATION:
            case ASTNode.ENUM_DECLARATION:
            case ASTNode.ANNOTATION_TYPE_DECLARATION:
                return store.getCategoryIndex(MembersOrderPreferenceCache.TYPE_INDEX) * 2;
            case ASTNode.FIELD_DECLARATION:
                if (Modifier.isStatic(modifiers)) {
                    int index = store.getCategoryIndex(MembersOrderPreferenceCache.STATIC_FIELDS_INDEX) * 2;
                    if (Modifier.isFinal(modifiers)) {
                        return index; // first final static, then static
                    }
                    return index + 1;
                }
                return store.getCategoryIndex(MembersOrderPreferenceCache.FIELDS_INDEX) * 2;
            case ASTNode.INITIALIZER:
                if (Modifier.isStatic(modifiers)) {
                    return store.getCategoryIndex(MembersOrderPreferenceCache.STATIC_INIT_INDEX) * 2;
                }
                return store.getCategoryIndex(MembersOrderPreferenceCache.INIT_INDEX) * 2;
            case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION:
                return store.getCategoryIndex(MembersOrderPreferenceCache.METHOD_INDEX) * 2;
            case ASTNode.METHOD_DECLARATION:
                if (Modifier.isStatic(modifiers)) {
                    return store.getCategoryIndex(MembersOrderPreferenceCache.STATIC_METHODS_INDEX) * 2;
                }
                if (((MethodDeclaration)member).isConstructor()) {
                    return store.getCategoryIndex(MembersOrderPreferenceCache.CONSTRUCTORS_INDEX) * 2;
                }
                return store.getCategoryIndex(MembersOrderPreferenceCache.METHOD_INDEX) * 2;
            default:
                return 100;
        }
    }

    /**
     * Computes the insertion index to be used to add the given member to the
     * the list <code>container</code>.
     * @param member the member to add
     * @param container a list containing objects of type <code>BodyDeclaration</code>
     * @return the insertion index to be used
     */
    public static int getInsertionIndex(BodyDeclaration member, List<? extends BodyDeclaration> container) {
        int containerSize = container.size();

        MembersOrderPreferenceCache orderStore = JavaPlugin.getDefault().getMemberOrderPreferenceCache();

        int orderIndex = getOrderPreference(member, orderStore);

        int insertPos = containerSize;
        int insertPosOrderIndex = -1;

        for (int i = containerSize - 1; i >= 0; i--) {
            int currOrderIndex = getOrderPreference(container.get(i), orderStore);
            if (orderIndex == currOrderIndex) {
                if (insertPosOrderIndex != orderIndex) { // no perfect match yet
                    insertPos = i + 1; // after a same kind
                    insertPosOrderIndex = orderIndex; // perfect match
                }
            } else if (insertPosOrderIndex != orderIndex) { // not yet a perfect match
                if (currOrderIndex < orderIndex) { // we are bigger
                    if (insertPosOrderIndex == -1) {
                        insertPos = i + 1; // after
                        insertPosOrderIndex = currOrderIndex;
                    }
                } else {
                    insertPos = i; // before
                    insertPosOrderIndex = currOrderIndex;
                }
            }
        }
        return insertPos;
    }

    public static SimpleName getLeftMostSimpleName(Name name) {
        if (name instanceof SimpleName) {
            return (SimpleName)name;
        } else {
            final SimpleName[] result = new SimpleName[1];
            ASTVisitor visitor = new ASTVisitor() {
                @Override
                public boolean visit(QualifiedName qualifiedName) {
                    Name left = qualifiedName.getQualifier();
                    if (left instanceof SimpleName)
                        result[0] = (SimpleName)left;
                    else
                        left.accept(this);
                    return false;
                }
            };
            name.accept(visitor);
            return result[0];
        }
    }

    /**
     * Returns the topmost ancestor of <code>name</code> that is still a {@link org.eclipse.jdt.core.dom.Name}.
     * <p>
     * <b>Note:</b> The returned node may resolve to a different binding than the given <code>name</code>!
     *
     * @param name a name node
     * @return the topmost name
     * @see #getNormalizedNode(org.eclipse.jdt.core.dom.ASTNode)
     */
    public static Name getTopMostName(Name name) {
        Name result = name;
        while (result.getParent() instanceof Name) {
            result = (Name)result.getParent();
        }
        return result;
    }

//    /**
//     * Returns the topmost ancestor of <code>node</code> that is a {@link org.eclipse.jdt.core.dom.Type} (but not a {@link org.eclipse
// .jdt.core.dom.UnionType}).
//     * <p>
//     * <b>Note:</b> The returned node often resolves to a different binding than the given <code>node</code>!
//     *
//     * @param node the starting node, can be <code>null</code>
//     * @return the topmost type or <code>null</code> if the node is not a descendant of a type node
//     * @see #getNormalizedNode(org.eclipse.jdt.core.dom.ASTNode)
//     */
//    public static Type getTopMostType(ASTNode node) {
//        ASTNode result = null;
//        while (node instanceof Type && !(node instanceof UnionType)
//               || node instanceof Name
//               || node instanceof Annotation || node instanceof MemberValuePair
//               ||
//               node instanceof Expression) { // Expression could maybe be reduced to expression node types that can appear in an
// annotation
//            result = node;
//            node = node.getParent();
//        }
//
//        if (result instanceof Type)
//            return (Type)result;
//
//        return null;
//    }
//
//    public static int changeVisibility(int modifiers, int visibility) {
//        return (modifiers & CLEAR_VISIBILITY) | visibility;
//    }
//
//    /**
//     * Adds flags to the given node and all its descendants.
//     * @param root The root node
//     * @param flags The flags to set
//     */
//    public static void setFlagsToAST(ASTNode root, final int flags) {
//        root.accept(new GenericVisitor(true) {
//            @Override
//            protected boolean visitNode(ASTNode node) {
//                node.setFlags(node.getFlags() | flags);
//                return true;
//            }
//        });
//    }

    public static String getQualifier(Name name) {
        if (name.isQualifiedName()) {
            return ((QualifiedName)name).getQualifier().getFullyQualifiedName();
        }
        return ""; //$NON-NLS-1$
    }

    public static String getSimpleNameIdentifier(Name name) {
        if (name.isQualifiedName()) {
            return ((QualifiedName)name).getName().getIdentifier();
        } else {
            return ((SimpleName)name).getIdentifier();
        }
    }

    public static boolean isDeclaration(Name name) {
        if (name.isQualifiedName()) {
            return ((QualifiedName)name).getName().isDeclaration();
        } else {
            return ((SimpleName)name).isDeclaration();
        }
    }

    public static Modifier findModifierNode(int flag, List<IExtendedModifier> modifiers) {
        for (int i = 0; i < modifiers.size(); i++) {
            Object curr = modifiers.get(i);
            if (curr instanceof Modifier && ((Modifier)curr).getKeyword().toFlagValue() == flag) {
                return (Modifier)curr;
            }
        }
        return null;
    }

    public static ITypeBinding getTypeBinding(CompilationUnit root, IType type) throws JavaModelException {
        if (type.isAnonymous()) {
            final IJavaElement parent = type.getParent();
            if (parent instanceof IField && Flags.isEnum(((IMember)parent).getFlags())) {
                final EnumConstantDeclaration constant =
                        (EnumConstantDeclaration)NodeFinder.perform(root, ((ISourceReference)parent).getSourceRange());
                if (constant != null) {
                    final AnonymousClassDeclaration declaration = constant.getAnonymousClassDeclaration();
                    if (declaration != null)
                        return declaration.resolveBinding();
                }
            } else {
                final ClassInstanceCreation creation =
                        (ClassInstanceCreation)getParent(NodeFinder.perform(root, type.getNameRange()), ClassInstanceCreation.class);
                if (creation != null)
                    return creation.resolveTypeBinding();
            }
        } else {
            final AbstractTypeDeclaration declaration =
                    (AbstractTypeDeclaration)getParent(NodeFinder.perform(root, type.getNameRange()), AbstractTypeDeclaration.class);
            if (declaration != null)
                return declaration.resolveBinding();
        }
        return null;
    }

    /**
     * Escapes a string value to a literal that can be used in Java source.
     *
     * @param stringValue the string value
     * @return the escaped string
     * @see org.eclipse.jdt.core.dom.StringLiteral#getEscapedValue()
     */
    public static String getEscapedStringLiteral(String stringValue) {
        StringLiteral stringLiteral = AST.newAST(ASTProvider.SHARED_AST_LEVEL).newStringLiteral();
        stringLiteral.setLiteralValue(stringValue);
        return stringLiteral.getEscapedValue();
    }

    /**
     * Escapes a character value to a literal that can be used in Java source.
     *
     * @param ch the character value
     * @return the escaped string
     * @see org.eclipse.jdt.core.dom.CharacterLiteral#getEscapedValue()
     */
    public static String getEscapedCharacterLiteral(char ch) {
        CharacterLiteral characterLiteral = AST.newAST(ASTProvider.SHARED_AST_LEVEL).newCharacterLiteral();
        characterLiteral.setCharValue(ch);
        return characterLiteral.getEscapedValue();
    }

    /**
     * Type-safe variant of {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite#createMoveTarget(org.eclipse.jdt.core.dom.ASTNode)}.
     *
     * @param rewrite ASTRewrite for the given node
     * @param node the node to create a move placeholder for
     * @return the new placeholder node
     * @throws IllegalArgumentException if the node is null, or if the node
     * is not part of the rewrite's AST
     */
    @SuppressWarnings("unchecked")
    public static <T extends ASTNode> T createMoveTarget(ASTRewrite rewrite, T node) {
        return (T)rewrite.createMoveTarget(node);
    }

    /**
     * Type-safe variant of {@link org.eclipse.jdt.core.dom.ASTNode#copySubtree(org.eclipse.jdt.core.dom.AST, org.eclipse.jdt.core.dom
 .ASTNode)}.
     *
     * @param target the AST that is to own the nodes in the result
     * @param node the node to copy, or <code>null</code> if none
     * @return the copied node, or <code>null</code> if <code>node</code>
     *    is <code>null</code>
     */
    @SuppressWarnings("unchecked")
    public static <T extends ASTNode> T copySubtree(AST target, T node) {
        return (T)ASTNode.copySubtree(target, node);
    }

    /**
     * Returns a list of local variable names which are visible at the given node.
     *
     * @param node the AST node
     * @return a list of local variable names visible at the given node
     * @see ScopeAnalyzer#getDeclarationsInScope(int, int)
	 * @since 3.10
	 */
	public static List<String> getVisibleLocalVariablesInScope(ASTNode node) {
		List<String> variableNames= new ArrayList<String>();
		CompilationUnit root= (CompilationUnit) node.getRoot();
		IBinding[] bindings= new ScopeAnalyzer(root).
				getDeclarationsInScope(node.getStartPosition(), ScopeAnalyzer.VARIABLES | ScopeAnalyzer.CHECK_VISIBILITY);
		for (IBinding binding : bindings) {
			if (binding instanceof IVariableBinding && !((IVariableBinding) binding).isField()) {
				variableNames.add(binding.getName());
			}
		}
		return variableNames;
	}
}
